package com.tangyh.lamp.common.util;

import cn.hutool.core.bean.BeanException;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.bean.copier.ValueProvider;
import cn.hutool.core.bean.copier.provider.BeanValueProvider;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.copier.Copier;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.TypeUtil;
import com.tangyh.basic.utils.CommonUtil;
import com.tangyh.lamp.common.annotation.TransField;
import lombok.NonNull;

import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;

/**
 * Bean 转换Map工具
 *
 * @version V1.0
 * @author: Sidon
 * @createTime: 2021-06-03 16:41
 * Copyright (C)2021-2021 福建省金柠网络科技有限公司. All rights reserved.
 */

public class BeanTransUtil extends BeanUtil {

    /**
     * Bean转Map,key全部转换为小写
     *
     * @param source 源对象
     * @return Map对象
     */
    @SuppressWarnings("unchecked")
    public static Map<String, Object> beanToMapToLowerCase(Object source) {
        return BeanCopier.build(source, new LinkedHashMap(), TransOptions.build().setLowerCase(true)).copy();
    }

    public static Map<String, Object> beanToMapUpperFirst(Object source) {
        return BeanCopier.build(source, new LinkedHashMap(), TransOptions.build().setUpperFirst(true)).copy();
    }

    /**
     * Bean转Map,同时根据{@link TransField}注解的value设置key的名称
     *
     * @param source 源对象
     * @return Map对象
     */
    @SuppressWarnings("unchecked")
    public static Map<String, Object> beanToMapToTransField(Object source) {
        return BeanCopier.build(source, new LinkedHashMap(), TransOptions.build().setTransField(true)).copy();
    }

    /**
     * Bean转TreeMap,根据key排序
     *
     * @param source          源对象
     * @param ignoreNullValue 是否忽略空值的字段
     * @return Map对象
     */
    @SuppressWarnings("unchecked")
    public static Map<String, Object> beanToTreeMap(Object source, boolean ignoreNullValue) {
        return BeanCopier.build(source, new TreeMap(), TransOptions.build().setTransField(true).setIgnoreNullValue(
                ignoreNullValue).setIgnoreNonAnnotation(true)).copy();
    }

    /**
     * 对象或Map转Bean，忽略字段转换时发生的异常
     *
     * @param <T>       转换的Bean类型
     * @param source    Bean对象或Map
     * @param clazz     目标的Bean类型
     * @param trimValue 删除首位空格
     * @return Bean对象
     * @since 5.4.0
     */
    public static <T> T toBeanTrimValue(Object source, Class<T> clazz, boolean trimValue) {
        final T target = ReflectUtil.newInstanceIfPossible(clazz);
        copyProperties(source, target, TransOptions.build().setIgnoreCase(true).setIgnoreError(true).setTrimValue(
                trimValue));
        return target;
    }

    public static void copyProperties(Object source, Object target, TransOptions copyOptions) {
        if (null == copyOptions) {
            copyOptions = new TransOptions();
        }
        BeanCopier.build(source, target, copyOptions).copy();
    }

    /**
     * Bean复制器，主要用于将Bean转换为Map对象
     *
     * @param <T>
     */
    static class BeanCopier<T> implements Copier<T> {
        /**
         * 元对象
         */
        private final Object source;
        /**
         * 目标对象
         */
        private final T dest;

        /**
         * 数据转换条件
         */
        private TransOptions transOptions;

        /**
         * 目标的类型（用于泛型类注入）
         */
        private final Type destType;

        private BeanCopier(Object source, T dest, TransOptions transOptions, Type destType) {
            this.source = source;
            this.dest = dest;
            this.transOptions = transOptions;
            this.destType = destType;
        }

        @NonNull
        public static <T> BeanCopier<T> build(Object source, T dest, TransOptions transOptions) {
            return new BeanCopier<>(source, dest, transOptions, dest.getClass());
        }

        @Override
        public T copy() {
            if (this.dest instanceof Map) {
                beanToMap(this.source, (Map<?, ?>) this.dest);
            } else {
                beanToBean(this.source, this.dest);
            }
            return dest;
        }

        @SuppressWarnings({"rawtypes", "unchecked"})
        private void beanToMap(Object bean, Map targetMap) {

            descForEach(bean.getClass(), (prop) -> {
                String key;
                TransField transField = prop.getField().getAnnotation(TransField.class);

                boolean ignoreNonAnnotation = transOptions.ignoreNonAnnotation;

                if (!(ignoreNonAnnotation && transField == null)) {
                    if (transOptions.toLowerCase) {
                        key = prop.getFieldName().toLowerCase();
                    } else if (transOptions.toTransField && transField != null) {
                        key = transField.value();
                    } else if (transOptions.upperFirst) {
                        key = CharSequenceUtil.upperFirst(prop.getFieldName());
                    } else {
                        key = prop.getFieldName();
                    }
                    // 对key做映射，映射后为null的忽略之
                    Object value;
                    try {
                        value = prop.getValue(bean);
                    } catch (Exception e) {
                        throw new BeanException(e, "Get value of [{}] error!", prop.getFieldName());
                    }
                    if (CommonUtil.isNotEmpty(value) || !transOptions.ignoreNullValue) {
                        if (transField != null && transField.isDate()) {
                            value = BaseDateUtil.getDateTime(value, transField.datePattern());
                        }
                        targetMap.put(key, value);
                    }
                }

            });
        }

        /**
         * Bean和Bean之间属性拷贝
         *
         * @param providerBean 来源Bean
         * @param destBean     目标Bean
         */
        private void beanToBean(Object providerBean, Object destBean) {
            valueProviderToBean(
                    new BeanValueProvider(providerBean, this.transOptions.ignoreCase, this.transOptions.ignoreError),
                    destBean);
        }

        /**
         * 值提供器转Bean<br>
         * 此方法通过遍历目标Bean的字段，从ValueProvider查找对应值
         *
         * @param valueProvider 值提供器
         * @param bean          Bean
         */
        private void valueProviderToBean(ValueProvider<String> valueProvider, Object bean) {
            if (null == valueProvider) {
                return;
            }

            final TransOptions transOptions = this.transOptions;
            Class<?> actualEditable = bean.getClass();
            if (null != transOptions.editable) {
                // 检查限制类是否为target的父类或接口
                if (!transOptions.editable.isInstance(bean)) {
                    throw new IllegalArgumentException(
                            StrUtil.format("Target class [{}] not assignable to Editable class [{}]",
                                    bean.getClass().getName(), transOptions.editable.getName()));
                }
                actualEditable = transOptions.editable;
            }
            final HashSet<String> ignoreSet = (null != transOptions.ignoreProperties) ? CollUtil.newHashSet(
                    transOptions.ignoreProperties) : null;

            // 遍历目标bean的所有属性
            BeanUtil.descForEach(actualEditable, (prop) -> {
                if (!prop.isWritable(this.transOptions.isTransientSupport())) {
                    // 字段不可写，跳过之
                    return;
                }
                // 检查属性名
                String fieldName = prop.getFieldName();
                if (CollUtil.contains(ignoreSet, fieldName)) {
                    // 目标属性值被忽略或值提供者无此key时跳过
                    return;
                }

                final String providerKey = transOptions.getMappedFieldName(fieldName, true);
                if (!valueProvider.containsKey(providerKey)) {
                    // 无对应值可提供
                    return;
                }

                // 获取目标字段真实类型
                final Type fieldType = TypeUtil.getActualType(this.destType, prop.getFieldType());

                // 获取属性值
                Object value = valueProvider.value(providerKey, fieldType);
                if ((null == value && transOptions.ignoreNullValue) || bean == value) {
                    // 当允许跳过空时，跳过
                    // 值不能为bean本身，防止循环引用
                    return;
                }

                if (transOptions.trimValue) {
                    if (value instanceof String) {
                        value = String.valueOf(value).trim();
                    }
                }

                prop.setValue(bean, value, transOptions.ignoreNullValue, transOptions.ignoreError);
            });
        }
    }


    /**
     * Bean转换的设置选项
     */
    static class TransOptions extends CopyOptions {

        protected String[] ignoreProperties;
        /**
         * 限制的类或接口，必须为目标对象的实现接口或父类，用于限制拷贝的属性，例如一个类我只想复制其父类的一些属性，就可以将editable设置为父类
         */
        protected Class<?> editable;

        protected boolean trimValue = false;

        protected boolean ignoreCase = false;

        protected boolean ignoreError = false;
        protected boolean upperFirst = false;

        /**
         * 使用{@link TransField}注解的别名字段
         */
        protected boolean toTransField = false;
        /**
         * 是否将key转换为小写
         */
        protected boolean toLowerCase = false;

        protected boolean ignoreNullValue = false;

        protected boolean ignoreNonAnnotation = false;

        /**
         * 反向映射表，自动生成用于反向查找
         */
        private Map<String, String> reversedFieldMapping;

        public static TransOptions build() {
            return new TransOptions();
        }

        public TransOptions setTransField(boolean toTransField) {
            this.toTransField = toTransField;
            return this;
        }

        public TransOptions setLowerCase(boolean toLowerCase) {
            this.toLowerCase = toLowerCase;
            return this;
        }

        @Override
        public TransOptions setIgnoreNullValue(boolean ignoreNullValue) {
            this.ignoreNullValue = ignoreNullValue;
            return this;
        }

        public TransOptions setIgnoreNonAnnotation(boolean ignoreNonAnnotation) {
            this.ignoreNonAnnotation = ignoreNonAnnotation;
            return this;
        }

        @Override
        public TransOptions setIgnoreError(boolean ignoreError) {
            this.ignoreError = ignoreError;
            return this;
        }

        public TransOptions setUpperFirst(boolean upperFirst) {
            this.upperFirst = upperFirst;
            return this;
        }

        @Override
        public TransOptions setIgnoreCase(boolean ignoreCase) {
            this.ignoreCase = ignoreCase;
            return this;
        }

        public TransOptions setTrimValue(boolean trimValue) {
            this.trimValue = trimValue;
            return this;
        }


        /**
         * 获得映射后的字段名<br>
         * 当非反向，则根据源字段名获取目标字段名，反之根据目标字段名获取源字段名。
         *
         * @param fieldName 字段名
         * @param reversed  是否反向映射
         * @return 映射后的字段名
         */
        @Override
        protected String getMappedFieldName(String fieldName, boolean reversed) {
            Map<String, String> mapping = reversed ? getReversedMapping() : this.fieldMapping;
            if (MapUtil.isEmpty(mapping)) {
                return fieldName;
            }
            return ObjectUtil.defaultIfNull(mapping.get(fieldName), fieldName);
        }

        /**
         * 获取反转之后的映射
         *
         * @return 反转映射
         * @since 4.1.10
         */
        private Map<String, String> getReversedMapping() {
            if (null == this.fieldMapping) {
                return null;
            }
            if (null == this.reversedFieldMapping) {
                reversedFieldMapping = MapUtil.reverse(this.fieldMapping);
            }
            return reversedFieldMapping;
        }
    }
}
